2 追蹤者

工作階段與 Cookies

工作階段與 cookies 允許資料在多個使用者請求之間持續存在。在純 PHP 中,您可以分別透過全域變數 $_SESSION$_COOKIE 存取它們。Yii 將工作階段和 cookies 封裝為物件,因此允許您以物件導向的方式存取它們,並具有額外的實用增強功能。

工作階段

如同 請求回應,您可以透過 session 應用程式組件 存取工作階段,預設情況下,它是 yii\web\Session 的實例。

開啟與關閉工作階段

要開啟和關閉工作階段,您可以執行以下操作

$session = Yii::$app->session;

// check if a session is already open
if ($session->isActive) ...

// open a session
$session->open();

// close a session
$session->close();

// destroys all data registered to a session.
$session->destroy();

您可以多次呼叫 open()close() 而不會導致錯誤;在內部,這些方法會先檢查工作階段是否已開啟。

存取工作階段資料

要存取儲存在工作階段中的資料,您可以執行以下操作

$session = Yii::$app->session;

// get a session variable. The following usages are equivalent:
$language = $session->get('language');
$language = $session['language'];
$language = isset($_SESSION['language']) ? $_SESSION['language'] : null;

// set a session variable. The following usages are equivalent:
$session->set('language', 'en-US');
$session['language'] = 'en-US';
$_SESSION['language'] = 'en-US';

// remove a session variable. The following usages are equivalent:
$session->remove('language');
unset($session['language']);
unset($_SESSION['language']);

// check if a session variable exists. The following usages are equivalent:
if ($session->has('language')) ...
if (isset($session['language'])) ...
if (isset($_SESSION['language'])) ...

// traverse all session variables. The following usages are equivalent:
foreach ($session as $name => $value) ...
foreach ($_SESSION as $name => $value) ...

資訊:當您透過 session 組件存取工作階段資料時,如果之前尚未完成,則會自動開啟工作階段。這與透過 $_SESSION 存取工作階段資料不同,後者需要明確呼叫 session_start()

當處理作為陣列的工作階段資料時,session 組件有一個限制,會阻止您直接修改陣列元素。例如,

$session = Yii::$app->session;

// the following code will NOT work
$session['captcha']['number'] = 5;
$session['captcha']['lifetime'] = 3600;

// the following code works:
$session['captcha'] = [
    'number' => 5,
    'lifetime' => 3600,
];

// the following code also works:
echo $session['captcha']['lifetime'];

您可以使用以下其中一種解決方案來解決此問題

$session = Yii::$app->session;

// directly use $_SESSION (make sure Yii::$app->session->open() has been called)
$_SESSION['captcha']['number'] = 5;
$_SESSION['captcha']['lifetime'] = 3600;

// get the whole array first, modify it and then save it back
$captcha = $session['captcha'];
$captcha['number'] = 5;
$captcha['lifetime'] = 3600;
$session['captcha'] = $captcha;

// use ArrayObject instead of array
$session['captcha'] = new \ArrayObject;
...
$session['captcha']['number'] = 5;
$session['captcha']['lifetime'] = 3600;

// store array data by keys with a common prefix
$session['captcha.number'] = 5;
$session['captcha.lifetime'] = 3600;

為了獲得更好的效能和程式碼可讀性,我們建議最後一種解決方案。也就是說,不要將陣列儲存為單個工作階段變數,而是將每個陣列元素儲存為與其他陣列元素共享相同金鑰字首的工作階段變數。

自訂工作階段儲存

預設的 yii\web\Session 類別將工作階段資料儲存為伺服器上的檔案。Yii 也提供了以下實作不同工作階段儲存的工作階段類別

  • yii\web\DbSession:將工作階段資料儲存在資料庫表格中。
  • yii\web\CacheSession:借助已設定的 快取組件,將工作階段資料儲存在快取中。
  • yii\redis\Session:使用 redis 作為儲存媒介來儲存工作階段資料。
  • yii\mongodb\Session:將工作階段資料儲存在 MongoDB 中。

所有這些工作階段類別都支援相同的 API 方法集。因此,您可以切換到不同的工作階段儲存類別,而無需修改使用工作階段的應用程式程式碼。

注意:如果您想在使用自訂工作階段儲存時透過 $_SESSION 存取工作階段資料,則必須確保工作階段已由 yii\web\Session::open() 啟動。這是因為自訂工作階段儲存處理常式是在此方法中註冊的。

注意:如果您使用自訂工作階段儲存,您可能需要明確設定工作階段垃圾回收器。某些 PHP 安裝(例如 Debian)使用 0 的垃圾回收器機率,並在 cronjob 中離線清理工作階段檔案。此過程不適用於您的自訂儲存,因此您需要設定 yii\web\Session::$GCProbability 以使用非零值。

要了解如何設定和使用這些組件類別,請參閱它們的 API 文件。以下範例展示如何在應用程式設定中設定 yii\web\DbSession 以使用資料庫表格進行工作階段儲存

return [
    'components' => [
        'session' => [
            'class' => 'yii\web\DbSession',
            // 'db' => 'mydb',  // the application component ID of the DB connection. Defaults to 'db'.
            // 'sessionTable' => 'my_session', // session table name. Defaults to 'session'.
        ],
    ],
];

您還需要建立以下資料庫表格來儲存工作階段資料

CREATE TABLE session
(
    id CHAR(40) NOT NULL PRIMARY KEY,
    expire INTEGER,
    data BLOB
)

其中 'BLOB' 指的是您偏好的 DBMS 的 BLOB 類型。以下是一些常用 DBMS 可以使用的 BLOB 類型

  • MySQL:LONGBLOB
  • PostgreSQL:BYTEA
  • MSSQL:BLOB

注意:根據 session.hash_function 的 php.ini 設定,您可能需要調整 id 欄位的長度。例如,如果 session.hash_function=sha256,您應該使用長度 64 而不是 40。

或者,這可以使用以下遷移來完成

<?php

use yii\db\Migration;

class m170529_050554_create_table_session extends Migration
{
    public function up()
    {
        $this->createTable('{{%session}}', [
            'id' => $this->char(64)->notNull(),
            'expire' => $this->integer(),
            'data' => $this->binary()
        ]);
        $this->addPrimaryKey('pk-id', '{{%session}}', 'id');
    }

    public function down()
    {
        $this->dropTable('{{%session}}');
    }
}

快閃資料

快閃資料是一種特殊的工作階段資料,一旦在一個請求中設定,則僅在下一個請求期間可用,並在之後自動刪除。快閃資料最常用於實作僅應向最終使用者顯示一次的訊息,例如使用者成功提交表單後顯示的確認訊息。

您可以透過 session 應用程式組件設定和存取快閃資料。例如,

$session = Yii::$app->session;

// Request #1
// set a flash message named as "postDeleted"
$session->setFlash('postDeleted', 'You have successfully deleted your post.');

// Request #2
// display the flash message named "postDeleted"
echo $session->getFlash('postDeleted');

// Request #3
// $result will be false since the flash message was automatically deleted
$result = $session->hasFlash('postDeleted');

與常規工作階段資料一樣,您可以將任意資料儲存為快閃資料。

當您呼叫 yii\web\Session::setFlash() 時,它將覆寫任何具有相同名稱的現有快閃資料。要將新的快閃資料附加到具有相同名稱的現有訊息,您可以改為呼叫 yii\web\Session::addFlash()。例如

$session = Yii::$app->session;

// Request #1
// add a few flash messages under the name of "alerts"
$session->addFlash('alerts', 'You have successfully deleted your post.');
$session->addFlash('alerts', 'You have successfully added a new friend.');
$session->addFlash('alerts', 'You are promoted.');

// Request #2
// $alerts is an array of the flash messages under the name of "alerts"
$alerts = $session->getFlash('alerts');

注意:盡量不要對相同名稱的快閃資料同時使用 yii\web\Session::setFlash()yii\web\Session::addFlash()。這是因為後一種方法會自動將快閃資料變成陣列,以便它可以附加相同名稱的新快閃資料。因此,當您呼叫 yii\web\Session::getFlash() 時,您可能會發現有時您會得到一個陣列,而有時您會得到一個字串,具體取決於這兩種方法的調用順序。

提示:對於顯示快閃訊息,您可以透過以下方式使用 yii\bootstrap\Alert 小工具

echo Alert::widget([
   'options' => ['class' => 'alert-info'],
   'body' => Yii::$app->session->getFlash('postDeleted'),
]);

Cookies

Yii 將每個 cookie 表示為 yii\web\Cookie 的物件。yii\web\Requestyii\web\Response 都透過名為 cookies 的屬性維護 cookie 集合。前者中的 cookie 集合表示在請求中提交的 cookie,而後者中的 cookie 集合表示要發送給使用者的 cookie。

應用程式中直接處理請求和回應的部分是控制器。因此,cookie 應在控制器中讀取和發送。

讀取 Cookies

您可以使用以下程式碼取得目前請求中的 cookies

// get the cookie collection (yii\web\CookieCollection) from the "request" component
$cookies = Yii::$app->request->cookies;

// get the "language" cookie value. If the cookie does not exist, return "en" as the default value.
$language = $cookies->getValue('language', 'en');

// an alternative way of getting the "language" cookie value
if (($cookie = $cookies->get('language')) !== null) {
    $language = $cookie->value;
}

// you may also use $cookies like an array
if (isset($cookies['language'])) {
    $language = $cookies['language']->value;
}

// check if there is a "language" cookie
if ($cookies->has('language')) ...
if (isset($cookies['language'])) ...

發送 Cookies

您可以使用以下程式碼將 cookies 發送給最終使用者

// get the cookie collection (yii\web\CookieCollection) from the "response" component
$cookies = Yii::$app->response->cookies;

// add a new cookie to the response to be sent
$cookies->add(new \yii\web\Cookie([
    'name' => 'language',
    'value' => 'zh-CN',
]));

// remove a cookie
$cookies->remove('language');
// equivalent to the following
unset($cookies['language']);

除了上述範例中顯示的 namevalue 屬性外,yii\web\Cookie 類別還定義了其他屬性,以完整表示所有可用的 cookie 資訊,例如 domainexpire。您可以根據需要設定這些屬性以準備 cookie,然後將其新增至回應的 cookie 集合。

當您透過 requestresponse 組件讀取和發送 cookies 時,如最後兩個小節所示,您可以享受 cookie 驗證的額外安全性,這可以保護 cookies 免受客戶端修改。這是透過使用雜湊字串簽署每個 cookie 來實現的,這允許應用程式判斷 cookie 是否已在客戶端修改。如果是,則無法透過 request 組件的 cookie 集合 存取 cookie。

注意:Cookie 驗證僅保護 cookie 值免受修改。如果 cookie 未通過驗證,您仍然可以透過 $_COOKIE 存取它。這是因為第三方程式庫可能會以自己的方式操作 cookies,這不涉及 cookie 驗證。

Cookie 驗證預設為啟用。您可以透過將 yii\web\Request::$enableCookieValidation 屬性設定為 false 來停用它,儘管我們強烈建議您不要這樣做。

注意:透過 $_COOKIEsetcookie() 直接讀取/發送的 Cookies 將不會經過驗證。

當使用 cookie 驗證時,您必須指定一個 yii\web\Request::$cookieValidationKey,它將用於產生上述雜湊字串。您可以透過在應用程式設定中設定 request 組件來執行此操作

return [
    'components' => [
        'request' => [
            'cookieValidationKey' => 'fill in a secret key here',
        ],
    ],
];

資訊:cookieValidationKey 對於您的應用程式的安全性至關重要。它應該僅為您信任的人所知。不要將其儲存在版本控制系統中。

安全性設定

yii\web\Cookieyii\web\Session 都支援以下安全性標誌

httpOnly

為了提高安全性,yii\web\Cookie::$httpOnlyyii\web\Session::$cookieParams 的 'httponly' 參數的預設值設定為 true。這有助於降低客戶端腳本存取受保護 cookie 的風險(如果瀏覽器支援)。您可以閱讀 HttpOnly wiki 文章 以了解更多詳細資訊。

secure

secure 標誌的目的是防止 cookies 以明文形式發送。如果瀏覽器支援 secure 標誌,則僅在透過安全 (TLS) 連線發送請求時才會包含 cookie。您可以閱讀 SecureFlag wiki 文章 以了解更多詳細資訊。

sameSite

從 Yii 2.0.21 開始,支援 yii\web\Cookie::$sameSite 設定。它需要 PHP 版本 7.3.0 或更高版本。sameSite 設定的目的是防止 CSRF(跨站請求偽造)攻擊。如果瀏覽器支援 sameSite 設定,它將僅根據指定的策略('Lax' 或 'Strict')包含 cookie。您可以閱讀 SameSite wiki 文章 以了解更多詳細資訊。為了提高安全性,如果 sameSite 與不受支援的 PHP 版本一起使用,將會拋出例外。要在不同的 PHP 版本中使用此功能,請先檢查版本。例如:

[
    'sameSite' => PHP_VERSION_ID >= 70300 ? yii\web\Cookie::SAME_SITE_LAX : null,
]

注意:由於並非所有瀏覽器都支援 sameSite 設定,因此仍然強烈建議也包含 額外的 CSRF 保護

工作階段 php.ini 設定

正如 PHP 手冊中指出php.ini 具有重要的工作階段安全性設定。請確保應用建議的設定。尤其是 PHP 安裝中預設未啟用的 session.use_strict_mode。此設定也可以透過 yii\web\Session::$useStrictMode 設定。

發現錯字或您認為此頁面需要改進?
在 github 上編輯 !